package roborally.itemsonboard;

import roborally.board.Board;
import roborally.supporting.Position;
import roborally.supporting.LegalStatus;
import exception.IllegalPositionOnBoardException;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Model;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * A class representing all items that have a position an possibly a board.
 * 
 * @invar The boardItem must have a valid position at all times.
 * 		| canHaveAsPosition(this.getPosition())
 * @invar The boardItem must have a proper board at all times.
 * 		| hasProperBoard()
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 2.0
 */
public abstract class BoardItemImpl implements LegalStatus, BoardItem {
	
//Constructors ----------------------------------------------------------------------------------------------------------
	
	/**
	 * Creates a new boardItem
	 * 
	 * @param board The board at which this item should be, null is allowed
	 * @param position The position at which this item should be.
	 * @effect Sets the board to the given board.
	 * 		| setBoard(board)
	 * @effect Sets the position to the given position.
	 * 		| setPosition(position)
	 * @throws IllegalArgumentException
	 * 		Position is null or the board can't have this new boardItem.
	 * 		| position== null || ( board != null && !board.canHaveAsItem(new this) )
	 */
	@Model
	protected BoardItemImpl(Board board, Position position) throws IllegalArgumentException{
		setPosition(position); // will never throw an exception (unless position is null) since here, board is null so all positions but null are allowed.
		if(board == null)
			setBoard(null);
		else
			board.addAsItem(this);
	}
	
//Terminatable ----------------------------------------------------------------------------------------------------------	
	
	/**
	 * Terminates this item. The item is removed from the board, if it is placed at one.
	 * 
	 * @post This item is removed from the board, if it has a board.
	 * 		| if(this.getBoard() != null)
	 * 		| 	for each item in this.getBoard().getItemsAt(this.getPosition())
	 * 		|		item != this
	 * @post The board of this item is set to null.
	 * 		| (new this).getBoard()==null
	 * @post This item is terminated.
	 * 		| (new this).isTerminated() == true
	 */
	@Override
	public void terminate() {
		if(getBoard() != null) {
			this.getBoard().removeAsItem(this);
		}
		this.isTerminated=true;
	}
	
	/**
	 * Returns whether this boardItem is terminated.
	 */
	@Override @Basic @Raw
	public boolean isTerminated(){
		return this.isTerminated;
	}
	
	/**
	 * A boolean indicating whether this boardItem is terminated.
	 */
	private boolean isTerminated = false;
	
//Board ----------------------------------------------------------------------------------------------------------	
	
	/**
	 * Returns the board on which this item is located, or null if it isn't located on any board.
	 */
	@Override
	@Basic @Raw
	public Board getBoard(){
		return this.board;
	}
	
	/**
	 * Sets the board of this item. Null is allowed.
	 * 
	 * @param board The board at which this item should be set. Null is allowed.
	 * @pre Board must be null or the board must already contain this item at the right position (so the board is Raw)
	 * 		| board==null || board.getItemsAt(this.getPosition()).contains(this)
	 * @post The board is set to the given value.
	 * 		| this.getBoard() == board
	 * @throws IllegalArgumentException
	 * 		If this is not a legal board.
	 * 		| !this.canHaveAsBoard(board)
	 */
	@Override
	public void setBoard(@Raw Board board) throws IllegalArgumentException { // the board is Raw because this item should already be on the board, but the board of this item is not that board.
		assert (board==null || board.getItemsAt(this.getPosition()).contains(this));
		if(canHaveAsBoard(board))
			this.board=board;
		else
			throw new IllegalArgumentException("This is not a valid board.");
	}
	
	/**
	 * Checks whether this boardItem can have the board as its board.
	 * 
	 * @param board The board to be checked.
	 * @return true if and only if board is null or the board can have this boardItem
	 * 		| result == ( board == null || board.canHaveAsItem(this) )
	 */
	@Override
	public boolean canHaveAsBoard(Board board) {
		return board == null || board.canHaveAsItem(this);
	}
	
	/**
	 * Checks whether this item has a valid board.
	 * 
	 * @return true if and only if this item can have this board and or the board is null or if the board contains this item at its position.
	 * 		| if !canHaveAsBoard(getBoard())
	 * 		|	then result == false
	 * 		| else if getBoard() == null 
	 * 		|	then result == true
	 * 		| if getBoard().getItemsAt(getPosition()).contains(this)
	 * 		|	then result == true
	 * 		| else result == false
	 */
	@Override
	public boolean hasProperBoard() {
		if(!canHaveAsBoard(getBoard())) 
			return false;
		if(getBoard() == null)
			return true;
		if (getBoard().getItemsAt(getPosition()).contains(this))
			return true;
		return false;
	}
	
	private Board board;
	
//Position ----------------------------------------------------------------------------------------------------------	
	
	/**
	 * Returns the position  of this item.
	 */
	@Override
	@Basic @Raw
	public Position getPosition() {
		return this.position;
	}
	
	/**
	 * Sets the position to a given value (without energy restrictions)
	 * 
	 * @param position The position to be set.
	 * @post The position is set to the given value.
	 * 		| (new this).getPosition() == position
	 * @throws IllegalArgumentException
	 * 		The item can't have the given position and or the board is null or the position is null.
	 * 		| !canHaveAsPosition(position) && position == null
	 * @throws IllegalPositionOnBoardException
	 * 		The item is placed on a board and can not be moved to the given position on the board.
	 * 		| !canHaveAsPosition(position) && this.getBoard() != null && position != null
	 */
	@Override
	public void setPosition(Position position) throws IllegalArgumentException, IllegalPositionOnBoardException {
		if(!canHaveAsPosition(position))
			if(this.getBoard() != null && position != null)
				throw new IllegalPositionOnBoardException("The object can not have the position on the board.",this, this.getBoard(), position);
			else // position is null
				throw new IllegalArgumentException("position-value is not valid");
		if(this.getBoard() != null) {
			Board tempBoard = this.getBoard();
			tempBoard.removeAsItem(this);
			this.position = position;
			tempBoard.addAsItem(this);
		}
		else 
			this.position = position;
	}
		
	/**
	 * Controls if the position is a valid position.
	 * 
	 * @param position The position to be controlled.
	 * @return true if and only if position is not null 
	 * 					and or the board is null or the board can contain this item at this position.
	 * 		| if position == null
	 * 		|	then result == false
	 * 		| else if this.getBoard() == null
	 * 		|	then result == true
	 * 		| else 
	 * 		| 	result == canHaveAsPositionOnBoard(position)
	 */ 
	@Override
	@Raw
	public boolean canHaveAsPosition(Position position) {
		if(position == null)
			return false;
		if(this.getBoard() == null)
			return true;
		else
			return canHaveAsPositionOnBoard(position);
	}
	
	/**
	 * Returns if this item can have the position on his board.
	 * 
	 * @param position The position to be controlled.
	 * @pre This must have a board.
	 * 		| this.getBoard() != null
	 * @pre The position may not be null.
	 * 		| position != null
	 * @return True if the position is on the board and no other objects are obstructing the given position.
	 * 		| let temp = this.getBoard().canHaveAsPosition(position)
	 * 		| for each boardItem in {board.getItemsAt(position)}
	 * 		|	if !canHaveItemOnSamePosition(boardItem)
	 * 		|		then temp = false 
	 * 		| result == temp
	 */
	@Override
	@Raw
	public boolean canHaveAsPositionOnBoard(Position position) {
		assert position != null;
		assert this.getBoard() != null;
		for(BoardItem item : this.getBoard().getItemsAt(position))
			if (!item.canHaveItemOnSamePosition(this))
				return false;
		return this.getBoard().canHaveAsPosition(position);
	}
	
	/**
	 * The position of this boardItem.
	 */
	private Position position;
	
//Object ----------------------------------------------------------------------------------------------------------	
	
	/**
	 * A boardItem may not be cloned.
	 * 
	 * @throws CloneNotSupportedException
	 * 		When this method is evoked.
	 * 		| true
	 */
	@Override
	public final BoardItem clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException();
	}
	
	/**
	 * Returns a textual specification of this boardItem.
	 * 
	 * @return A textual specification of this boardItem.
	 * 		| result == return this.getClass().getSimpleName() + ": [" + this.getPosition().toString() + "]," +
	 *		|	" [On board: " + (getBoard() != null) + "]";
	 */
	@Override
	public String toString() {
		return this.getClass().getSimpleName() + ": [" + this.getPosition().toString() + "]," +
				" [On board: " + (getBoard() != null) + "], [Legal status: " + legalStatus() + "], [Terminated: " +
				this.isTerminated() + "]";
	}
	
//LegalStatus ----------------------------------------------------------------------------------------------------------
	
	/**
	 * Controls if the boardItem satisfies all his class invariants.
	 * 
	 * @return False if the board is improperly.
	 * 		| if !hasProperBoard()
	 * 		|	then result == false
	 * @return False if the position is not legal.
	 * 		| if !canHaveAsPosition(this.getPosition())
	 * 		|	then result == false
	 */
	@Override
	public boolean legalStatus() {
		return  hasProperBoard() && canHaveAsPosition(this.getPosition());
	}
}

